/* A moz-remote service for browsing the web
  Copyright (C) 2006 by Lukas Loehrer
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA

  Author: Lukas Loehrer <loehrerl [at] gmx [dot] net>
*/

const logger = module.require("package", "lib/filelogger").logger;

// This is a service which can emit events
var EventService = module.require("class", "event_service");
var inheritor = new EventService();

const BrowserWindow = module.require("class", "browser/browser_window");
const util = module.require("package", "mozremote/util");
const acc_util = module.require("package", "browser/accessibility_util");

const Ci = Components.interfaces;

function constructor(name, remote) {
	EventService.call(this, name, remote);
	// TODO: fix this! Cleaner access to remoting service is needed.
	this._remoting = remote._services._services["Remoting"];
	this._browsers = [];
	this._chromeWindowName = "Firemacs-Window";
	/* This is a hack because I did not manage to create the window on
	   demand in such a way that it is only returned when it is ready.
	   So I create it here hoping it will be ready by the time it is
	   actually needed.
	*/
	this._getFiremacsWindow();  // for side effects...

	var _this = this;
// Be informed when new windows pop up
	this._windowListener = {
		onCloseWindow: function (win) {},
		onOpenWindow: function (win) {
			_this._onNewWindow(win);
		},
		onWindowTitleChange: function (win, newTitle) {}
	};
	util.service("@mozilla.org/appshell/window-mediator;1", "nsIWindowMediator")
		.addListener(this._windowListener);

	// TabOpen event handler 
	this._tabOpenHandler = function (event) {
		logger.debug("In tabopenhandler.");
		if (!_this._inhibitTabOpenHandler) {
			_this._onNewTab(event.target, event.originalTarget);
		}
	};
	this._inhibitTabOpenHandler = false;
	this._getTabBrowser().addEventListener("TabOpen", this._tabOpenHandler, true);

// TabClose event handler 
this._tabCloseHandler = function (event) {
		_this._onCloseTab(event.target, event.originalTarget);
	};
//this._getTabBrowser().addEventListener("TabClose", this._tabCloseHandler, true);

	// The following makes it easier to get notified when the DOM changes
	this._accEventObserver = new acc_util.AccessibleEventObserver(this);
	
	// Events for browser windows
	this._events["load-complete"] = true;
	this._events["frame-load-complete"] = true;
	this._events["load-start"] = true;
	this._events["load-abort"] = true;
	this._events["subtree-modified"] = true;
	this._events["state-change"] = true;
	this._events["internal-jump"] = true;
	// Events for browser service
this._events["new-browser"] = true;
}

function _disconnectService() {
	EventService.prototype._disconnectService.call(this);

	this._accEventObserver.unregister();
	this._getTabBrowser().removeEventListener("TabOpen", this._tabOpenHandler, true);
	this._getTabBrowser().removeEventListener("TabClose", this._tabCloseHandler, true);
	util.service("@mozilla.org/appshell/window-mediator;1", "nsIWindowMediator")
		.removeListener(this._windowListener);
	for each (var browser in this._browsers.slice()) {
		logger.debug("Closing " + browser);
		this.closeBrowserWindow(browser);
	}
	logger.debug("this._browsers.length=" + this._browsers.length);
}

// {{{Remotely callable functions 

function getNewBrowserWindow() {
	var browser = this._createBrowserWindow(this._getNewBrowserInTab());
	return this.bless(browser);
}

function closeBrowserWindow(aBrowserWindow) {
	aBrowserWindow.closeInternal();
	this._removeBrowserWindow(aBrowserWindow);
	return true;
}

// }}}
// {{{Helper functions 

function bless(obj) {
	this._remoting.bless(obj);
	return obj;
}

function _createBrowserWindow(browser) {
var result  = new BrowserWindow(this, browser);
	this._browsers.push(result);
	return result;
}

/* Returns the chrome window used by this remoting service for its content windows.
 */
function _getFiremacsWindow() {
	var win;

	win = util.getWindowByName(this._chromeWindowName);
	if (!win) {
	win = util.openReadyWindowWithName(this._chromeWindowName);
	}
// TODO: make sure win is actually a chrom window

	return win;
}

function _getTabBrowser() {
	var win = this._getFiremacsWindow();
	if (win) {
		return win.getBrowser();
	} else {
		return null;
	}
}

function _getNewBrowserInTab() {
	var win = this._getFiremacsWindow();
	var tb = win.getBrowser();
	try {
		this._inhibitTabOpenHandler = true;
		var tab = tb.addTab("about:blank");
		return tb.getBrowserForTab(tab);
	} finally {
		this._inhibitTabOpenHandler = false;
	}
}

function getBrowserForDOMWindow(aWindow) {
	// Make sure we are getting the top most frame
	aWindow = aWindow.top;
	// Compare with all windows under our control
	for each (var browser in this._browsers) {
		if (browser._getContentWindow() == aWindow) {
			return browser;
		}
	}
	return null;
}


// }}}
// {{{Event handlers 

// called by the AccessibleEventObserver if an interesting event occurs
function onAccessibleEvent(aEventType, aAccEvent, aAccessible) {
	try {
		// First, we check if this event concerns us at all
		// 1. Did it occur in an HTML document?
		// 2. Did it occur in a document under firemacs' control? Which one?
		var accDocument = aAccEvent.accessibleDocument;
		var DOMDocument = accDocument.document;
		// We are currently only considering events in HTML documents
		if (DOMDocument == null || !(DOMDocument instanceof Ci.nsIDOMHTMLDocument)) {
			return;
		}
		if (accDocument && (accDocument.finalState & Ci.nsIAccessible.STATE_BUSY)) {
			// Ignore events that occur while the document is loading
			// Not sure if this is such a good idea.
			return;
		}

		var DOMWindow = accDocument.window;
	
		var win = this.getBrowserForDOMWindow(DOMWindow);	
		if (win == null) {
			// event does not concern any of the windows under our control
			return;
		}

		var DOMNode = aAccEvent.DOMNode;
		logger.debug("browser_service.onAccessibleEvent: type=" + acc_util.IAccEventNames[aEventType] + " role=" + acc_util.IAccRoleNames[aAccessible.finalRole] + " title=" + DOMDocument.title);
		switch (aEventType) {
		case Ci.nsIAccessibleEvent.EVENT_REORDER:
			win.onSubtreeModified(DOMNode);
			break;
		case Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE:
			win.onStateChange(DOMNode);
			break;
		}
	} catch (e) {
		logger.error("browser_service.onAccessibleEvent: " + e, e);
	}
}

function _onNewTab(aTabBrowser, aTab) {
	logger.info("browser_service._onNewTab: new tab opened by itself.");
	try {
		var browser = aTabBrowser.getBrowserForTab(aTab);
		var browserWindow = this._createBrowserWindow(browser);

		var event = {
			browser: this.bless(browserWindow),
			title: browser.contentDocument.title,
			url: ((browser.currentURI) ? browser.currentURI.spec : "")
		};
		this._fireEvent("new-browser", event);
	} catch (e) {
		logger.error("browser_service._onNewTab: " + e, e);
	}
}

function _onCloseTab(aTabBrowser, aTab) {
	var browser = aTabBrowser.getBrowserForTab(aTab);
	var browserWindow = this.getBrowserForDOMWindow(browser.contentWindow);

	if (browserWindow && !browserWindow.closing) {
		this.closeBrowserWindow(browserWindow);
	}

	/*
	  // Tell emacs that tab is closing
	var event = {
		browser: this.bless(browser)
	};
	this._fireEvent("close-browser", event);
	*/
}


// Called when a new window pops up for what ever reason
function _onNewWindow(win) {
	logger.debug("browser_service: A new window just opened.");
}



function _removeBrowserWindow(aBrowserWindow) {
	var index = this._browsers.indexOf(aBrowserWindow);
	if (index >= 0) {
		this._browsers.splice(index, 1);
	}
}

// }}}

// {{{ Emacs local variables

// local variables:
// folded-file: t
// end:

// }}}
